home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / admin / uucp / uujobs < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  44.3 KB  |  1,251 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) uujobs.gawk 1.3 97/01/06
  4. # uujobs: print relationships between uucp files.
  5. # 92/03/04 john@armory.com (John H. DuBois III)
  6. # 92/03/20 converted into #!awk script, changed output format
  7. # 92/09/20 Fixed to correctly print nonexistant referenced files;
  8. #          when -u is given, print full filenames; added s option.
  9. # 92/09/25 Convince awk that RemoteEx is an array; added w option;
  10. #          added address format conversion to -s option.
  11. # 92/10/26 Added j and d options.
  12. # 92/11/23 Display originator & remote cmd for X.* files.
  13. # 93/11/22 Added c, C, o, O, k, r, and i options.
  14. # 93/12/19 Major cleanup.  Added printing of input file.
  15. # 94/03/09 Use gawk so - options can be given
  16.  
  17. BEGIN {
  18.     if ((ArgsLeft = DoOptions(ARGC,ARGV)) == -1)
  19.     exit
  20.  
  21.     if (!u)
  22.     Format = SetupFormat(S,ListDatafiles,IDonly)
  23.  
  24.     NumFiles = FindFiles(JobsGiven,ArgsLeft,ARGV,Files)
  25.     ProcFilenames(Files,NumFiles,u,JobsGiven,ListDatafiles)
  26. }
  27.  
  28. # Calls ProcNames once for each site, with the file names for that site.
  29. function ProcFilenames(Files,NumFiles,u,JobsGiven,ListDatafiles,
  30. i,Comp,Site,File,LastSite,C,D,X) {
  31.     # Convince awk that X, D, and C are arrays so that (foo in [XDC])
  32.     # can be used without error even if no files have been put in them.
  33.     split("",C," ")
  34.     split("",D," ")
  35.     split("",X," ")
  36.  
  37.     for (i = 1; i <= NumFiles; i++) {
  38.     split(Files[i],Comp,"/")
  39.     Site = Comp[1]
  40.     File = Comp[2]
  41.     if (LastSite != "" && Site != LastSite) {
  42.         ProcNames(C,D,X,LastSite,u,!JobsGiven,ListDatafiles)
  43.         EmptyArr(C)
  44.         EmptyArr(D)
  45.         EmptyArr(X)
  46.     }
  47.     # Add filename to C[], D[], or X[]
  48.     AddFileName(File,C,D,X)
  49.     LastSite = Site
  50.     }
  51.     if (Site != "")
  52.     ProcNames(C,D,X,Site,u,!JobsGiven,ListDatafiles)
  53. }
  54.  
  55. # Find datafiles as specified
  56. function FindFiles(JobsGiven,ArgsLeft,ARGV,Files,  Pattern,i,Cmd,LineNum) {
  57.     if (JobsGiven) {
  58.     Pattern = "*/D.*"
  59.     for (i = 1; i <= ARGC; i++)
  60.         if (i in ARGV)
  61.         Pattern = Pattern " */[CX]." ARGV[i]
  62.     }
  63.     else if (ArgsLeft < 2) # If no sitenames given, check all sites
  64.     Pattern = "*/[CDX].*"
  65.     else
  66.     for (i = 1; i <= ARGC; i++)
  67.         if (i in ARGV)
  68.         Pattern = Pattern ARGV[i] "/[CDX].* "
  69.     Cmd = "cd /usr/spool/uucp; for file in " Pattern \
  70.     "; do [ -f \"$file\" ] && echo $file; done 2>&1"
  71.     LineNum = 0
  72.     while (Cmd | getline)
  73.     Files[++LineNum] = $0
  74.     if (JobsGiven)
  75.     qsortNumIndByValue(Files,1,LineNum)
  76.     return LineNum
  77. }
  78.  
  79. # Prints headers & returns printf format string for data lines
  80. function SetupFormat(S,ListDatafiles,IDonly,  Format) {
  81.     if (S) {
  82.     Format = "%-12s %-8s %-29s %s"
  83.     printf Format "\n","Job ID","Site","Originator","Remote command"
  84.     }
  85.     else if (ListDatafiles)
  86.     Format = "%-12s %s"
  87.     else if (!IDonly) {
  88.     Format = "%-8s %-14s %-14s %s"
  89.     printf Format "\n","Site","Originator","Remote command",""
  90.     printf Format "\n","","Job","Input file","Other data files"
  91.     }
  92.     return Format
  93. }
  94.  
  95. # Sets globals:
  96. # Width, CommandPat, NotCommandPat, OriginatorPat, NotOriginatorPat,
  97. # S, IDonly, u, Kill, Rejuvenate, ListDatafiles, JobsGiven
  98. function DoOptions(ARGC,ARGV,  ArgsLeft) {
  99.     ArgsLeft = ProcArgs(ARGC,ARGV,"dhijkrsuw:c:C:o:O:",Options) 
  100.     if (ArgsLeft == -1) {
  101.     printf "Error: %s\n",OptErr | "cat 1>&2"
  102.     close("cat 1>&2")
  103.     return -1
  104.     }
  105.     if (Options["h"]) {
  106.     Help()
  107.     return -1
  108.     }
  109.     Width = Options["w"]
  110.  
  111.     # Ignore leading & trailing whitespace.
  112.     if ("c" in Options)
  113.     CommandPat = "^[ \t]*" Options["c"] "[ \t]*$"
  114.     if ("C" in Options)
  115.     NotCommandPat = "^[ \t]*" Options["C"] "[ \t]*$"
  116.     if ("o" in Options)
  117.     OriginatorPat = "^[ \t]*" Options["o"] "[ \t]*$"
  118.     if ("O" in Options)
  119.     NotOriginatorPat = "^[ \t]*" Options["O"] "[ \t]*$"
  120.     S = Options["s"]
  121.     IDonly = Options["i"]
  122.     if (IDonly && Width) {
  123.     printf "Error: do not use the 'w' option with the 'i' option.\n" \
  124.     | "cat 1>&2"
  125.     return -1
  126.     }
  127.     u = Options["u"]
  128.     Kill = Options["k"]
  129.     Rejuvenate = Options["r"]
  130.     ListDatafiles = Options["d"]
  131.     JobsGiven = Options["j"]
  132.     return ArgsLeft
  133. }
  134.  
  135. function Help() {
  136.     print \
  137. "uujobs: print uucp jobs.\n" \
  138. "Usage: uujobs [-dhikrsu] [-w<width>] [-[cC]<command>] [-[oO]<originator>]\n"\
  139. "       [sitename ...|-j<job-ID> ...]\n" \
  140. "For each job, the site, originator, command, and filenames are printed.\n" \
  141. "For rmail jobs, the sender of the mail is printed as the originator.\n" \
  142. "For other jobs, the job owner listed in the command file is printed.\n" \
  143. "If site names are given, only the jobs waiting for them are processed.\n" \
  144. "If no site names are given, all waiting uucp jobs are processed.\n" \
  145. "Options:\n" \
  146. "-c: Output is limited to jobs containing a particular remote command.\n"\
  147. "    <command> can be a pattern in the style of egrep(C).  The entire path\n"\
  148. "    and arguments of a command must match it.  To match regardless of\n"\
  149. "    arguments, end the pattern with '.*' (e.g.: -c'rmail .*').\n"\
  150. "-C: All jobs except those containing a matching command are printed.\n"\
  151. "-d: For each job, list the job ID followed by the data files it references.\n"\
  152. "-h: Print this help.\n" \
  153. "-i: List job IDs only, one per line, with no header.\n" \
  154. "-j: List only the jobs whose job IDs are given on the command line.\n" \
  155. "-k, -r: kill or rejuvenate the listed jobs with uustat(C).\n"\
  156. "-o, -O: like -c and -C, except for the originator of a job.\n"\
  157. "-u: List only unreferenced files and files that reference noexistant files.\n"\
  158. "-s: Short form: list job ID, site, originator, and command on a single\n"\
  159. "    line; paths are shortened and converted to domain format if possible.\n" \
  160. "-w: Truncate displayed lines to at most <width> characters.\n" \
  161. "    The header is not truncated."
  162. }
  163.  
  164. function EmptyArr(A,  name) {
  165.     for (name in A)
  166.     delete A[name]
  167. }
  168.  
  169. # Makes File an element of set C, D, or X depending on what letter it
  170. # starts with.  For C and X files, the leading C. or X. is removed.
  171. function AddFileName(File,C,D,X) {
  172.     if (File ~ "^C")
  173.     C[substr(File,3)]
  174.     else if (File ~ "^D")
  175.     D[File]
  176.     else if (File ~ "^X")
  177.     X[substr(File,3)]
  178.     else
  179.     printf "File \"%s\" ignored.\n",File | "cat 1>&2"
  180. }
  181.  
  182. # ProcNames is called once for each site, with C[], D[], and X[] being
  183. # the names of the outgoing-command job IDs, job-data files, and
  184. # incoming-execute job IDs waiting for for processing for that site.
  185. # The existance of a particular job ID indicates the existance of a
  186. # C.job-ID or X.job-ID file.
  187. # Site is the site they belong to.
  188. # u is true if only unreferenced files should be printed.
  189. # PrintUnref is true if warnings about unreferenced files should be printed.
  190. # ListDatafiles is true if a per-job datafile list should be printed.
  191. function ProcNames(C,D,X,Site,u,PrintUnref,ListDatafiles,
  192. Job2Cmd,RemoteEx,dir,file,ret,DataF2Job,SourceFile,DestFile,DataFile,
  193. Sender,O,Job,Job2DataF,F) {
  194.     # DataF2Job[] gives the job ID that owns each data file.
  195.     split("",DataF2Job)
  196.     # The indices of RemoteEx[] will be made the names of datafiles that
  197.     # will be execute files on the remote system.
  198.     split("",RemoteEx)
  199.     split("",O)
  200.     dir = "/usr/spool/uucp/" Site "/"
  201.  
  202.     # Read all command files
  203.     for (Job in C)
  204.     ReadCmdFile(Job,dir,DataF2Job,Job2DataF,RemoteEx,O)
  205.  
  206.     split("",Job2Cmd)
  207.     # Set DataF2Job and Job2Cmd for execute (X) files
  208.     ReadXFiles(X,"X",Site,I,DataF2Job,Job2Cmd,O)
  209.     # Read the remote execute files to find what the remote commands are
  210.     # and what the input files to those commands will be
  211.     ReadXFiles(RemoteEx,"D",Site,I,DataF2Job,Job2Cmd,O)
  212.  
  213.     FindUnref(DataF2Job,D,u,PrintUnref)
  214.     if (!u) {
  215.     PrintLines(Job2DataF,I,O,Site,Job2Cmd,ListDatafiles)
  216.     split("",Job2DataF)
  217.     PrintLines(X,I,O,Site,Job2Cmd,ListDatafiles)
  218.     }
  219. }
  220.  
  221. # Find unreferenced data files.
  222. function FindUnref(DataF2Job,D,u,PrintUnref,  file) {
  223.     for (file in DataF2Job) {
  224.     if (file in D)
  225.         delete D[file]
  226.     else {
  227.         if (u)
  228.         print dir DataF2Job[file]
  229.         else
  230.         printf "%s references nonexistant file %s.\n", \
  231.         DataF2Job[file],file | "cat 1>&2"
  232.     }
  233.     }
  234.     for (file in D) {
  235.     if (u)
  236.         print dir file
  237.     else if (PrintUnref)
  238.         printf "%s not referenced.\n",file | "cat 1>&2"
  239.     }
  240. }
  241.  
  242. # Read the command file for the specified job.
  243. # For each filename specified to be sent to the remote system,
  244. # set DataF2Job[filename] to the ID of this job.
  245. # Make the value of Job2DataF[Job] be a space-separated list of all of the
  246. # data files is uses.
  247. # If the datafile will be an execute (X.*) file after being transferred,
  248. # store the local name of the file in RemoteEx[] with the index being the
  249. # ID the job will have after transfer.
  250. # Set O[Job] to the originator of the job.
  251. function ReadCmdFile(Job,dir,DataF2Job,Job2DataF,RemoteEx,O,
  252. filename,ret,SourceFile,DestFile,Sender,DataFile) {
  253.     # Lines in command files are of the form:
  254.     # type sourcefile destfile sender options data mode notify
  255.     # type will be one of:
  256.     # S Send a file from local to a remote system
  257.     # R Copy a file from remote system to local system
  258.     # X Send an execution request to a remote system
  259.  
  260.     # For each command file, read the lines in it.
  261.     # For S (Send file) lines, make sure that referenced sourcefiles exist,
  262.     # add them to referenced set, & set C[cmdfile] to the files it references
  263.     filename = dir "C." Job
  264.     while ((ret = (getline < filename)) == 1) {
  265.     if ($1 != "S")
  266.         continue
  267.     SourceFile = $2
  268.     DestFile = $3
  269.     Sender = $4
  270.     DataFile = $6
  271.     DataF2Job[SourceFile] = Job
  272.     if (DestFile ~ /^X\./)
  273.         RemoteEx[substr(DestFile,3)] = SourceFile
  274.     # sourcefile is generally the same as datafile but not always
  275.     if (SourceFile == DataFile)
  276.         Job2DataF[Job] = Job2DataF[Job] SourceFile "  "
  277.     else {
  278.         Job2DataF[Job] = Job2DataF[Job] SourceFile "  " DataFile "  "
  279.         DataF2Job[DataFile] = Job
  280.     }
  281.     if (!(Job in O) && Sender != "")
  282.         O[Job] = Sender
  283.     }
  284.     close(filename)
  285.     if (ret)
  286.     printf "Read from command file \"%s\" failed.\n",filename | "cat 1>&2"
  287. }
  288.  
  289. # Read the contents of execute files (D.* on the originating system,
  290. # X.* on destination system).
  291. # X[] is passed with indices set to job IDs.  If the index has a non-null
  292. # value, it is the local name of the file that will be the execute file
  293. # on the remote end.
  294. # For each job in X[], X[job] is set to a space-separated list of any local
  295. # files it references.
  296. # The name of each local file referenced is also made an index of DataF2Job[].
  297. # The command for each execute file is put in Job2Cmd[job].
  298. # The name of the standard input file for the remote command is put in I[job].
  299. # Site should be the site that the files in X[] belong to.
  300. # Prefix is the file prefix, which with the job determine the filename.
  301. # For execute files that have been transferred from a remote system and
  302. # will be processed on this system, the prefix is X; for execute files
  303. # that wwere generated on this system & are currently datafiles waiting to
  304. # be transferred to the remote system, the prefix is D.
  305. # The requestor of the job (from the U line) is put in O[job].
  306. function ReadXFiles(X,Prefix,Site,I,DataF2Job,Job2Cmd,O,
  307. filename,PathPref,ret,Job) {
  308.     PathPref = "/usr/spool/uucp/" Site "/"
  309.     for (Job in X) {
  310.     # I gives the file to be used for standard input.
  311.     # F names a file to be transferred.
  312.     # C gives command to be executed.
  313.     if (X[Job] != "")
  314.         filename = PathPref X[Job]
  315.     else
  316.         filename = PathPref Prefix "." Job
  317.     while ((ret = (getline < filename)) == 1) {
  318.         if (($1 == "I") || ($1 == "F")) {
  319.         if ($1 == "I") {
  320.             I[Job] = $2
  321.         }
  322.         X[Job] = X[Job] $2 " "
  323.         DataF2Job[$2] = Job
  324.         }
  325.         else if ($1 == "C")
  326.         Job2Cmd[Job] = substr($0,3)
  327.         else if ($1 == "U" && !(Job in O))
  328.             O[Job] = $2
  329.     }
  330.     close(filename)
  331.     if (ret)
  332.         printf \
  333.         "Read from execute file \"%s\" failed.\n",filename | "cat 1>&2"
  334.     }
  335. }
  336.  
  337. # Print info on jobs
  338. # Job2DataF[job] gives space-separated lists of data files referenced jobs
  339. # I[job] gives the input file for each job
  340. # O[job] gives the originator of each job
  341. # Site is the site these jobs are for
  342. # Job2Cmd[job] gives the remote command that a job will run, if any
  343. # ListDatafiles is true if the output should just be a list of datafiles
  344. # referenced by each job
  345. function PrintLines(Job2DataF,I,O,Site,Job2Cmd,ListDatafiles,
  346. DataFiles,file,InputFile,Command,Originator,Job,i,Files) {
  347.     for (Job in Job2DataF) {
  348.     Command = Job2Cmd[Job]
  349.     InputFile = I[Job]
  350.     DataFiles = ""
  351.     split(Job2DataF[Job],Files)
  352.     for (i in Files) {
  353.         file = Files[i]
  354.         if (file != InputFile)
  355.         DataFiles = DataFiles file " "
  356.     }
  357.  
  358.     # for X.* DataFiles
  359.     if (Command == "" && "X." Job in Job2Cmd) {
  360.         Command = Job2Cmd["X." Job]
  361.     }
  362.  
  363.     # Restrict jobs displayed to those that match pattern.
  364.     if (CommandPat != "" && Command !~ CommandPat)
  365.         continue
  366.     if (NotCommandPat != "" && Command ~ NotCommandPat)
  367.         continue
  368.  
  369.     # This is lame but mmdf makes the UUCP originator of
  370.     # mail always be mmdf
  371.     if (Command ~ "^rmail ") {
  372.         if ((getline < InputFile) == 1 && $1 == "From")
  373.         Originator = $2
  374.         else {
  375.         Originator = "?"
  376.         }
  377.         close(InputFile)
  378.     }
  379.     else if (Job in O)
  380.         Originator = O[Job]
  381.     else
  382.         Originator = "-"
  383.  
  384.     # More restriction
  385.     if (OriginatorPat != "" && Originator !~ OriginatorPat)
  386.         continue
  387.     if (NotOriginatorPat != "" && (Originator ~ NotOriginatorPat))
  388.         continue
  389.  
  390.     if (S) {
  391.         Originator = To_Dom(Originator)
  392.         if (Width)
  393.         print substr(\
  394.         sprintf(Format,Job,Site,Originator,Command),1,Width)
  395.         else
  396.         printf Format "\n",Job,Site,Originator,Command
  397.     }
  398.     else if (ListDatafiles)
  399.         if (Width)
  400.         print substr(\
  401.         sprintf(Format "\n",Job,Job2DataF[Job]),1,Width)
  402.         else
  403.         printf(Format "\n",Job,Job2DataF[Job])
  404.     else if (IDonly)
  405.         print Job
  406.     else
  407. #        "Site","Originator","Remote command"
  408. #        "","Master Job","Input Job","Other data DataFiles"
  409.         if (Width) {
  410.         print substr(sprintf(Format,Site,Originator,Command,""),1,Width)
  411.         print \
  412.         substr(sprintf(Format,"",Job,InputFile,DataFiles),1,Width)
  413.         }
  414.         else {
  415.         printf Format "\n",Site,Originator,Command,""
  416.         printf Format "\n","",Job,InputFile,DataFiles
  417.         }
  418.     if (Kill)
  419.         system("uustat -k" Job)
  420.     if (Rejuvenate)
  421.         system("uustat -r" Job)
  422.     }
  423. }
  424.  
  425. # Convert uucp-format address Addr to domain format, if possible
  426. # If there are no site names, the user name is returned.
  427. # Otherwise, components of the path up to a component that looks like a domain
  428. # name are discarded.
  429. # If there is only one site name, user@sitename is returned.
  430. # If more than one, site!site!...!user is returned.
  431. function To_Dom(Addr,  Comp,NumComp,User,Path) {
  432.     if (Addr ~ ":@%")    # Not handled by this function
  433.     return Addr
  434.     # Find a suffix of the address that contains a site name that
  435.     # includes a '.', assumed to be a full domain name
  436.     # [^!]+\\.[^!]+    Domain-type site name
  437.     # !            Path separator  
  438.     # ([^.]+!)?        Optional non-domain-type site names
  439.     # [^!]+$        User name
  440.     if (match(Addr,"[^!]+\\.[^!]+!([^.]+!)?[^!]+$"))
  441.     Addr = substr(Addr,RSTART)
  442.     NumComp = split(Addr,Comp,"!")
  443.     User = Comp[NumComp--]
  444.     if (!NumComp)
  445.     return User
  446.     Path = Comp[NumComp--]
  447.     if (!NumComp)
  448.     return User "@" Path
  449.     return Addr
  450. }
  451.  
  452. ### Start of ProcArgs library
  453. # @(#) ProcArgs 1.11 96/12/08
  454. # 92/02/29 john h. dubois iii (john@armory.com)
  455. # 93/07/18 Added "#" arg type
  456. # 93/09/26 Do not count -h against MinArgs
  457. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  458. #          Removed meaning of "+" or "-" by itself.
  459. # 94/03/08 Added & option and *()< option types.
  460. # 94/04/02 Added NoRCopt to Opts()
  461. # 94/06/11 Mark numeric variables as such.
  462. # 94/07/08 Opts(): Do not require any args if h option is given.
  463. # 95/01/22 Record options given more than once.  Record option num in argv.
  464. # 95/06/08 Added ExclusiveOptions().
  465. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  466. #          Expand $VARNAME at the start of its filenames.
  467. #          Let varname=0 and -option- turn off an option.
  468. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  469. #          of the vars should be searched for in the environment.
  470. #          Check for duplicate rcfiles.
  471. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  472. #          now return various negatives values on error, not just -1, and
  473. #          Opts() may set Err to various positive values, not just 1.
  474. #          Added AllowUnrecOpt.
  475. # 96/05/23 Check type given for & option
  476. # 96/06/15 Re-port to awk
  477. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  478. #          used by other functions.
  479. # 96/10/15 Added OptChars
  480. # 96/11/01 Added exOpts arg to Opts()
  481. # 96/11/16 Added ; type
  482. # 96/12/08 Added Opt2Set() & Opt2Sets()
  483. # 96/12/27 Added CmdLineOpt()
  484.  
  485. # optlist is a string which contains all of the possible command line options.
  486. # A character followed by certain characters indicates that the option takes
  487. # an argument, with type as follows:
  488. # :    String argument
  489. # ;    Non-empty string argument
  490. # *    Floating point argument
  491. # (    Non-negative floating point argument
  492. # )    Positive floating point argument
  493. # #    Integer argument
  494. # <    Non-negative integer argument
  495. # >    Positive integer argument
  496. # The only difference the type of argument makes is in the runtime argument
  497. # error checking that is done.
  498.  
  499. # The & option is a special case used to get numeric options without the
  500. # user having to give an option character.  It is shorthand for [-+.0-9].
  501. # If & is included in optlist and an option string that begins with one of
  502. # these characters is seen, the value given to "&" will include the first
  503. # char of the option.  & must be followed by a type character other than ":"
  504. # or ";".
  505. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  506.  
  507. # Strings in argv[] which begin with "-" or "+" are taken to be
  508. # strings of options, except that a string which consists solely of "-"
  509. # or "+" is taken to be a non-option string; like other non-option strings,
  510. # it stops the scanning of argv and is left in argv[].
  511. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  512. # If an option takes an argument, the argument may either immediately
  513. # follow it or be given separately.
  514. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  515. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  516. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  517. # this feature had a flaw that caused problems in some cases.  See the OptChars
  518. # parameter to explicitly set the option-specifier characters.
  519.  
  520. # If an option that does not take an argument is given,
  521. # an index with its name is created in Options and its value is set to the
  522. # number of times it occurs in argv[].
  523.  
  524. # If an option that does take an argument is given, an index with its name is
  525. # created in Options and its value is set to the value of the argument given
  526. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  527. # If an option that takes an argument is given more than once,
  528. # Options[option-name,"count"] is incremented, and the value is assigned to
  529. # the index (option-name,instance) where instance is 2 for the second occurance
  530. # of the option, etc.
  531. # In other words, the first time an option with a value is encountered, the
  532. # value is assigned to an index consisting only of its name; for any further
  533. # occurances of the option, the value index has an extra (count) dimension.
  534.  
  535. # The sequence number for each option found in argv[] is stored in
  536. # Options[option-name,"num",instance], where instance is 1 for the first
  537. # occurance of the option, etc.  The sequence number starts at 1 and is
  538. # incremented for each option, both those that have a value and those that
  539. # do not.  Options set from a config file have a value of 0 assigned to this.
  540.  
  541. # Options and their arguments are deleted from argv.
  542. # Note that this means that there may be gaps left in the indices of argv[].
  543. # If compress is nonzero, argv[] is packed by moving its elements so that
  544. # they have contiguous integer indices starting with 0.
  545. # Option processing will stop with the first unrecognized option, just as
  546. # though -- was given except that unlike -- the unrecognized option will not be
  547. # removed from ARGV[].  Normally, an error value is returned in this case.
  548. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  549. # be found, so the number of remaining arguments is returned instead.
  550. # If OptChars is not a null string, it is the set of characters that indicate
  551. # that an argument is an option string if the string begins with one of the
  552. # characters.  A string consisting solely of two of the same option-indicator
  553. # characters stops the scanning of argv[].  The default is "-+".
  554. # argv[0] is not examined.
  555. # The number of arguments left in argc is returned.
  556. # If an error occurs, the global string OptErr is set to an error message
  557. # and a negative value is returned.
  558. # Current error values:
  559. # -1: option that required an argument did not get it.
  560. # -2: argument of incorrect type supplied for an option.
  561. # -3: unrecognized (invalid) option.
  562. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  563. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  564. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  565. {
  566. # ArgNum is the index of the argument being processed.
  567. # ArgsLeft is the number of arguments left in argv.
  568. # Arg is the argument being processed.
  569. # ArgLen is the length of the argument being processed.
  570. # ArgInd is the position of the character in Arg being processed.
  571. # Option is the character in Arg being processed.
  572. # Pos is the position in OptList of the option being processed.
  573. # NumOpt is true if a numeric option may be given.
  574.     ArgsLeft = argc
  575.     NumOpt = index(OptList,"&")
  576.     OptionNum = 0
  577.     if (OptChars == "")
  578.     OptChars = "-+"
  579.     while (OptChars != "") {
  580.     c = substr(OptChars,1,1)
  581.     OptChars = substr(OptChars,2)
  582.     OptCharSet[c]
  583.     OptTerm[c c]
  584.     }
  585.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  586.     Arg = argv[ArgNum]
  587.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  588.         break    # Not an option; quit
  589.     if (Arg in OptTerm) {
  590.         delete argv[ArgNum]
  591.         ArgsLeft--
  592.         break
  593.     }
  594.     ArgLen = length(Arg)
  595.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  596.         Option = substr(Arg,ArgInd,1)
  597.         if (NumOpt && Option ~ /[-+.0-9]/) {
  598.         # If this option is a numeric option, make its flag be & and
  599.         # its option string flag position be the position of & in
  600.         # the option string.
  601.         Option = "&"
  602.         Pos = NumOpt
  603.         # Prefix Arg with a char so that ArgInd will point to the
  604.         # first char of the numeric option.
  605.         Arg = "&" Arg
  606.         ArgLen++
  607.         }
  608.         # Find position of flag in option string, to get its type (if any).
  609.         # Disallow & as literal flag.
  610.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  611.         if (AllowUnrecOpt) {
  612.             Escape = 1
  613.             break
  614.         }
  615.         else {
  616.             OptErr = "Invalid option: " specGiven Option
  617.             return -3
  618.         }
  619.         }
  620.  
  621.         # Find what the value of the option will be if it takes one.
  622.         # NeedNextOpt is true if the option specifier is the last char of
  623.         # this arg, which means that if the option requires a value it is
  624.         # the next arg.
  625.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  626.         if (GotValue = ArgNum + 1 < argc)
  627.             Value = argv[ArgNum+1]
  628.         }
  629.         else {    # Value is included with option
  630.         Value = substr(Arg,ArgInd + 1)
  631.         GotValue = 1
  632.         }
  633.  
  634.         if (HadValue = AssignVal(Option,Value,Options,
  635.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  636.         specGiven)) {
  637.         if (HadValue < 0)    # error occured
  638.             return HadValue
  639.         if (HadValue == 2)
  640.             ArgInd++    # Account for the single-char value we used.
  641.         else {
  642.             if (NeedNextOpt) {    # option took next arg as value
  643.             delete argv[++ArgNum]
  644.             ArgsLeft--
  645.             }
  646.             break    # This option has been used up
  647.         }
  648.         }
  649.     }
  650.     if (Escape)
  651.         break
  652.     # Do not delete arg until after processing of it, so that if it is not
  653.     # recognized it can be left in ARGV[].
  654.     delete argv[ArgNum]
  655.     ArgsLeft--
  656.     }
  657.     if (compress != 0) {
  658.     dest = 1
  659.     src = argc - ArgsLeft + 1
  660.     for (count = ArgsLeft - 1; count; count--) {
  661.         ARGV[dest] = ARGV[src]
  662.         dest++
  663.         src++
  664.     }
  665.     }
  666.     return ArgsLeft
  667. }
  668.  
  669. # Assignment to values in Options[] occurs only in this function.
  670. # Option: Option specifier character.
  671. # Value: Value to be assigned to option, if it takes a value.
  672. # Options[]: Options array to return values in.
  673. # ArgType: Argument type specifier character.
  674. # GotValue: Whether any value is available to be assigned to this option.
  675. # Name: Name of option being processed.
  676. # OptionNum: Number of this option (starting with 1) if set in argv[],
  677. #     or 0 if it was given in a config file or in the environment.
  678. # SingleOpt: true if the value (if any) that is available for this option was
  679. #     given as part of the same command line arg as the option.  Used only for
  680. #     options from the command line.
  681. # specGiven is the option specifier character use, if any (e.g. - or +),
  682. # for use in error messages.
  683. # Global variables: OptErr
  684. # Return value: negative value on error, 0 if option did not require an
  685. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  686. # the arg.
  687. # Current error values:
  688. # -1: Option that required an argument did not get it.
  689. # -2: Value of incorrect type supplied for option.
  690. # -3: Bad type given for option &
  691. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  692. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  693.     # If option takes a value...    [
  694.     NumTypes = "*()#<>]"
  695.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  696.     OptErr = "Bad type given for & option"
  697.     return -3
  698.     }
  699.  
  700.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  701.     if (!GotValue) {
  702.         if (Name != "")
  703.         OptErr = "Variable requires a value -- " Name
  704.         else
  705.         OptErr = "option requires an argument -- " Option
  706.         return -1
  707.     }
  708.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  709.         OptErr = Err
  710.         return -2
  711.     }
  712.     # Mark this as a numeric variable; will be propogated to Options[] val.
  713.     if (ArgType != ":" && ArgType != ";")
  714.         Value += 0
  715.     if ((Instance = ++Options[Option,"count"]) > 1)
  716.         Options[Option,Instance] = Value
  717.     else
  718.         Options[Option] = Value
  719.     }
  720.     # If this is an environ or rcfile assignment & it was given a value...
  721.     else if (!OptionNum && Value != "") {
  722.     UsedValue = 1
  723.     # If the value is "0" or "-" and this is the first instance of it,
  724.     # do not set Options[Option]; this allows an assignment in an rcfile to
  725.     # turn off an option (for the simple "Option in Options" test) in such
  726.     # a way that it cannot be turned on in a later file.
  727.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  728.         Instance = 1
  729.     else
  730.         Instance = ++Options[Option]
  731.     # Save the value even though this is a flag
  732.     Options[Option,Instance] = Value
  733.     }
  734.     # If this is a command line flag and has a - following it in the same arg,
  735.     # it is being turned off.
  736.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  737.     UsedValue = 2
  738.     if (Option in Options)
  739.         Instance = ++Options[Option]
  740.     else
  741.         Instance = 1
  742.     Options[Option,Instance]
  743.     }
  744.     # If this is a flag assignment without a value, increment the count for the
  745.     # flag unless it was turned off.  The indicator for a flag being turned off
  746.     # is that the flag index has not been set in Options[] but it has an
  747.     # instance count.
  748.     else if (Option in Options || !((Option,1) in Options))
  749.     # Increment number of times this flag seen; will inc null value to 1
  750.     Instance = ++Options[Option]
  751.     Options[Option,"num",Instance] = OptionNum
  752.     return UsedValue
  753. }
  754.  
  755. # Option is the option letter
  756. # Value is the value being assigned
  757. # Name is the var name of the option, if any
  758. # ArgType is one of:
  759. # :    String argument
  760. # ;    Non-null string argument
  761. # *    Floating point argument
  762. # (    Non-negative floating point argument
  763. # )    Positive floating point argument
  764. # #    Integer argument
  765. # <    Non-negative integer argument
  766. # >    Positive integer argument
  767. # specGiven is the option specifier character use, if any (e.g. - or +),
  768. # for use in error messages.
  769. # Returns null on success, err string on error
  770. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  771.     if (ArgType == ":")
  772.     return ""
  773.     if (ArgType == ";") {
  774.     if (Value == "")
  775.         Err = "must be a non-empty string"
  776.     }
  777.     # A number begins with optional + or -, and is followed by a string of
  778.     # digits or a decimal with digits before it, after it, or both
  779.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  780.     Err = "must be a number"
  781.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  782.     Err = "may not include a fraction"
  783.     else if (ArgType ~ "[()<>]" && Value < 0)
  784.     Err = "may not be negative"
  785.     # (
  786.     else if (ArgType ~ "[)>]" && Value == 0)
  787.     Err = "must be a positive number"
  788.     if (Err != "") {
  789.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  790.     if (Name != "")
  791.         return ErrStr "variable " substr(Name,1,1) " " Err
  792.     else {
  793.         if (Option == "&")
  794.         Option = Value
  795.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  796.     }
  797.     }
  798.     else
  799.     return ""
  800. }
  801.  
  802. # Note: only the above functions are needed by ProcArgs.
  803. # The rest of these functions call ProcArgs() and also do other
  804. # option-processing stuff.
  805.  
  806. # Opts: Process command line arguments.
  807. # Opts processes command line arguments using ProcArgs()
  808. # and checks for errors.  If an error occurs, a message is printed
  809. # and the program is exited.
  810. #
  811. # Input variables:
  812. # Name is the name of the program, for error messages.
  813. # Usage is a usage message, for error messages.
  814. # OptList the option description string, as used by ProcArgs().
  815. # MinArgs is the minimum number of non-option arguments that this
  816. # program should have, non including ARGV[0] and +h.
  817. # If the program does not require any non-option arguments,
  818. # MinArgs should be omitted or given as 0.
  819. # rcFiles, if given, is a colon-seprated list of filenames to read for
  820. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  821. # by the value of the environment variable HOME.  If a filename begins with
  822. # $, the part from the character after the $ up until (but not including)
  823. # the first character not in [a-zA-Z0-9_] will be searched for in the
  824. # environment; if found its value will be substituted, if not the filename will
  825. # be discarded.
  826. # rcfiles are read in the order given.
  827. # Values given in them will not override values given on the command line,
  828. # and values given in later files will not override those set in earlier
  829. # files, because AssignVal() will store each with a different instance index.
  830. # The first instance of each variable, either on the command line or in an
  831. # rcfile, will be stored with no instance index, and this is the value
  832. # normally used by programs that call this function.
  833. # VarNames is a comma-separated list of variable names to map to options,
  834. # in the same order as the options are given in OptList.
  835. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  836. # searched for in the environment.  If set to -1, all values will be searched
  837. # for in the environment.  Values given in the environment will override
  838. # those given in the rcfiles but not those given on the command line.
  839. # NoRCopt, if given, is an additional letter option that if given on the
  840. # command line prevents the rcfiles from being read.
  841. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  842. # ExclusiveOptions() for a description of exOpts.
  843. # Special options:
  844. # If x is made an option and is given, some debugging info is output.
  845. # h is assumed to be the help option.
  846.  
  847. # Global variables:
  848. # The command line arguments are taken from ARGV[].
  849. # The arguments that are option specifiers and values are removed from
  850. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  851. # The number of elements in ARGV[] should be in ARGC.
  852. # After processing, ARGC is set to the number of elements left in ARGV[].
  853. # The option values are put in Options[].
  854. # On error, Err is set to a positive integer value so it can be checked for in
  855. # an END block.
  856. # Return value: The number of elements left in ARGV is returned.
  857. # Must keep OptErr global since it may be set by InitOpts().
  858. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  859. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  860.     if (MinArgs == "")
  861.     MinArgs = 0
  862.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  863.     optChars)
  864.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  865.     if (ArgsLeft >= 0) {
  866.         OptErr = "Not enough arguments"
  867.         Err = 4
  868.     }
  869.     else
  870.         Err = -ArgsLeft
  871.     printf "%s: %s.\nUse -h for help.\n%s\n",
  872.     Name,OptErr,Usage > "/dev/stderr"
  873.     exit 1
  874.     }
  875.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  876.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  877.     {
  878.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  879.     Err = -e
  880.     exit 1
  881.     }
  882.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  883.     {
  884.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  885.     Err = 1
  886.     exit 1
  887.     }
  888.     return ArgsLeft
  889. }
  890.  
  891. # ReadConfFile(): Read a file containing var/value assignments, in the form
  892. # <variable-name><assignment-char><value>.
  893. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  894. # line and whitespace between the variable name and the assignment character) 
  895. # is stripped.  Lines that do not contain an assignment operator or which
  896. # contain a null variable name are ignored, other than possibly being noted in
  897. # the return value.  If more than one assignment is made to a variable, the
  898. # first assignment is used.
  899. # Input variables:
  900. # File is the file to read.
  901. # Comment is the line-comment character.  If it is found as the first non-
  902. #     whitespace character on a line, the line is ignored.
  903. # Assign is the assignment string.  The first instance of Assign on a line
  904. #     separates the variable name from its value.
  905. # If StripWhite is true, whitespace around the value (whitespace between the
  906. #     assignment char and trailing whitespace on the line) is stripped.
  907. # VarPat is a pattern that variable names must match.  
  908. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  909. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  910. #     a line; no assignment operator is needed.  These variables are set in
  911. #     the output array with a null value.  Lines containing nothing but
  912. #     whitespace are still ignored.
  913. # Output variables:
  914. # Values[] contains the assignments, with the indexes being the variable names
  915. #     and the values being the assigned values.
  916. # Lines[] contains the line number that each variable occured on.  A flag set
  917. #     is record by giving it an index in Lines[] but not in Values[].
  918. # Return value:
  919. # If any errors occur, a string consisting of descriptions of the errors
  920. # separated by newlines is returned.  In no case will the string start with a
  921. # numeric value.  If no errors occur,  the number of lines read is returned.
  922. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  923. FlagsOK,
  924. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  925.     if (Comment != "")
  926.     Comment = "^" Comment
  927.     AssignLen = length(Assign)
  928.     if (VarPat == "")
  929.     VarPat = "."    # null varname not allowed
  930.     while ((Status = (getline Line < File)) == 1) {
  931.     LineNum++
  932.     sub("^[ \t]+","",Line)
  933.     if (Line == "")        # blank line
  934.         continue
  935.     if (Comment != "" && Line ~ Comment)
  936.         continue
  937.     if (Pos = index(Line,Assign)) {
  938.         Var = substr(Line,1,Pos-1)
  939.         Val = substr(Line,Pos+AssignLen)
  940.         if (StripWhite) {
  941.         sub("^[ \t]+","",Val)
  942.         sub("[ \t]+$","",Val)
  943.         }
  944.     }
  945.     else {
  946.         Var = Line    # If no value, var is entire line
  947.         Val = ""
  948.     }
  949.     if (!FlagsOK && Val == "") {
  950.         Errs = Errs \
  951.         sprintf("\nBad assignment on line %d of file %s: %s",
  952.         LineNum,File,Line)
  953.         continue
  954.     }
  955.     sub("[ \t]+$","",Var)
  956.     if (Var !~ VarPat) {
  957.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  958.         LineNum,File,Var)
  959.         continue
  960.     }
  961.     if (!(Var in Lines)) {
  962.         Lines[Var] = LineNum
  963.         if (Pos)
  964.         Values[Var] = Val
  965.     }
  966.     }
  967.     if (Status)
  968.     Errs = Errs "\nCould not read file " File
  969.     close(File)
  970.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  971. }
  972.  
  973. # Variables:
  974. # Data is stored in Options[].
  975. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  976. # Global vars:
  977. # Sets OptErr.  Uses ENVIRON[].
  978. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  979. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  980. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  981. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  982.     split("",filesRead,"")    # make awk know this is an array
  983.     NumVars = split(VarNames,Vars,",")
  984.     TypesInd = Ret = 0
  985.     if (EnvSearch == -1)
  986.     EnvSearch = NumVars
  987.     for (i = 1; i <= NumVars; i++) {
  988.     Var = Vars[i]
  989.     CharOpt = substr(OptList,++TypesInd,1)
  990.     if (CharOpt ~ "^[:;*()#<>&]$")
  991.         CharOpt = substr(OptList,++TypesInd,1)
  992.     Map[Var] = CharOpt
  993.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  994.     # Do not overwrite entries from environment
  995.     if (i <= EnvSearch && Var in ENVIRON &&
  996.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  997.         return Err
  998.     }
  999.  
  1000.     numrcFiles = split(rcFiles,fNames,":")
  1001.     for (i = 1; i <= numrcFiles; i++) {
  1002.     rcFile = fNames[i]
  1003.     if (rcFile ~ "^~/")
  1004.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1005.     else if (rcFile ~ /^\$/) {
  1006.         rcFile = substr(rcFile,2)
  1007.         match(rcFile,"^[a-zA-Z0-9_]*")
  1008.         envvar = substr(rcFile,1,RLENGTH)
  1009.         if (envvar in ENVIRON)
  1010.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1011.         else
  1012.         continue
  1013.     }
  1014.     if (rcFile in filesRead)
  1015.         continue
  1016.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1017.     # may be the same
  1018.     filesRead[rcFile]
  1019.     if ("x" in Options)
  1020.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1021.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1022.     if (retStr > 0)
  1023.         READ_RCFILE = 1
  1024.     else if (ret != "") {
  1025.         OptErr = retStr
  1026.         Ret = -1
  1027.     }
  1028.     for (Var in Lines)
  1029.         if (Var in Map) {
  1030.         if ((Err = AssignVal(Map[Var],
  1031.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1032.         Var in Values,Var,0)) < 0)
  1033.             return Err
  1034.         }
  1035.         else {
  1036.         OptErr = sprintf(\
  1037.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1038.         Lines[Var],rcFile)
  1039.         Ret = -1
  1040.         }
  1041.     }
  1042.  
  1043.     if ("x" in Options)
  1044.     for (Var in Map)
  1045.         if (Map[Var] in Options)
  1046.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1047.         "/dev/stderr"
  1048.         else
  1049.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1050.     return Ret
  1051. }
  1052.  
  1053. # OptSets is a semicolon-separated list of sets of option sets.
  1054. # Within a list of option sets, the option sets are separated by commas.  For
  1055. # each set of sets, if any option in one of the sets is in Options[] AND any
  1056. # option in one of the other sets is in Options[], an error string is returned.
  1057. # If no conflicts are found, nothing is returned.
  1058. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1059. # the exclusions presented by the first set of sets (ab,def,g) if:
  1060. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1061. # (a or b is in Options[]) AND (g is in Options) OR
  1062. # (d, e, or f is in Options[]) AND (g is in Options)
  1063. # An error will be returned due to the exclusions presented by the second set
  1064. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1065. # todo: make options given on command line unset options given in config file
  1066. # todo: that they conflict with.
  1067. function ExclusiveOptions(OptSets,Options,
  1068. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1069. SetNum,OSetNum) {
  1070.     NumSetSets = split(OptSets,SetSets,";")
  1071.     # For each set of sets...
  1072.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1073.     # NumSets is the number of sets in this set of sets.
  1074.     NumSets = split(SetSets[SetSet],Sets,",")
  1075.     # For each set in a set of sets except the last...
  1076.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1077.         s1 = Sets[SetNum]
  1078.         L1 = length(s1)
  1079.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1080.         # If any of the options in this set was given, check whether
  1081.         # any of the options in the other sets was given.  Only check
  1082.         # later sets since earlier sets will have already been checked
  1083.         # against this set.
  1084.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1085.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1086.             s2 = Sets[OSetNum]
  1087.             L2 = length(s2)
  1088.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1089.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1090.                 ErrStr = ErrStr "\n"\
  1091.                 sprintf("Cannot give both %s and %s options.",
  1092.                 c1,c2)
  1093.             }
  1094.     }
  1095.     }
  1096.     if (ErrStr != "")
  1097.     return substr(ErrStr,2)
  1098.     return ""
  1099. }
  1100.  
  1101. # The value of each instance of option Opt that occurs in Options[] is made an
  1102. # index of Set[].
  1103. # The return value is the number of instances of Opt in Options.
  1104. function Opt2Set(Options,Opt,Set,  count) {
  1105.     if (!(Opt in Options))
  1106.     return 0
  1107.     Set[Options[Opt]]
  1108.     count = Options[Opt,"count"]
  1109.     for (; count > 1; count--)
  1110.     Set[Options[Opt,count]]
  1111.     return count
  1112. }
  1113.  
  1114. # The value of each instance of option Opt that occurs in Options[] that
  1115. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1116. # Other values are made indexes of Set[].
  1117. # The return value is the number of instances of Opt in Options.
  1118. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1119.     ret = Opt2Set(Options,Opt,aSet)
  1120.     for (value in aSet)
  1121.     if (substr(value,1,1) == "!")
  1122.         nSet[substr(value,2)]
  1123.     else
  1124.         Set[value]
  1125.     return ret
  1126. }
  1127.  
  1128. # Returns true if option Opt was given on the command line.
  1129. function CmdLineOpt(Options,Opt,  i) {
  1130.     for (i = 1; (Opt,"num",i) in Options; i++)
  1131.     if (Options[Opt,"num",i] != 0)
  1132.         return 1
  1133.     return 0
  1134. }
  1135. ### End of ProcArgs library
  1136. ### Begin qsort routines
  1137.  
  1138. # Arr[] is an array of values with arbitrary indices.
  1139. # k[] is returned with numeric indices 1..n.
  1140. # The values in k[] are the indices of Arr[],
  1141. # ordered so that if Arr[] is stepped through
  1142. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1143. # through in order of the values of its elements.
  1144. # The return value is the number of elements in the arrays (n).
  1145. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  1146.     ElNum = 0
  1147.     for (ArrInd in Arr)
  1148.     k[++ElNum] = ArrInd
  1149.     qsortSegment(Arr,k,1,ElNum)
  1150.     return ElNum
  1151. }
  1152.  
  1153. # Sort a segment of an array.
  1154. # Arr[] contains data with arbitrary indices.
  1155. # k[] has indices 1..nelem, with the indices of arr[] as values.
  1156. # This function sorts the elements of arr that are pointed to by
  1157. # k[start..end], swapping the values of elements of k[] so that
  1158. # when this function returns arr[k[start..end]] will be in order.
  1159. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1160.     # handle two-element case explicitly for a tiny speedup
  1161.     if ((end - start) == 1) {
  1162.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  1163.         k[start] = tmpe
  1164.         k[end] = tmps
  1165.     }
  1166.     return
  1167.     }
  1168.     # Make sure comparisons act on these as numbers
  1169.     left = start+0
  1170.     right = end+0
  1171.     sepval = Arr[k[int((left + right) / 2)]]
  1172.     # Make every element <= sepval be to the left of every element > sepval
  1173.     while (left < right) {
  1174.     while (Arr[k[left]] < sepval)
  1175.         left++
  1176.     while (Arr[k[right]] > sepval)
  1177.         right--
  1178.     if (left < right) {
  1179.         tmp = k[left]
  1180.         k[left++] = k[right]
  1181.         k[right--] = tmp
  1182.     }
  1183.     }
  1184.     if (left == right)
  1185.     if (Arr[k[left]] < sepval)
  1186.         left++
  1187.     else
  1188.         right--
  1189.     if (start < right)
  1190.     qsortSegment(Arr,k,start,right)
  1191.     if (left < end)
  1192.     qsortSegment(Arr,k,left,end)
  1193. }
  1194.  
  1195. # Arr[] is an array of values with arbitrary indices.
  1196. # k[] is returned with numeric indices 1..n.
  1197. # The values in k are the indices of Arr[],
  1198. # ordered so that if Arr[] is stepped through
  1199. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1200. # through in order of the values of its indices.
  1201. # The return value is the number of elements in the arrays (n).
  1202. # If the indexes are numeric, Numeric should be true, so that they can be
  1203. # compared as such rather than as strings.  Numeric indexes do not have to be
  1204. # contiguous.
  1205. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  1206.     ElNum = 0
  1207.     if (Numeric)
  1208.     # Indexes do not preserve numeric type, so must be forced
  1209.     for (ArrInd in Arr)
  1210.         k[++ElNum] = ArrInd+0
  1211.     else
  1212.     for (ArrInd in Arr)
  1213.         k[++ElNum] = ArrInd
  1214.     qsortNumIndByValue(k,1,ElNum)
  1215.     return ElNum
  1216. }
  1217.  
  1218. # Arr is an array of elements with contiguous numeric indexes to be sorted
  1219. # by value.
  1220. # start and end are the starting and ending indexes of the range to be sorted.
  1221. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1222.     # handle two-element case explicitly for a tiny speedup
  1223.     if ((start - end) == 1) {
  1224.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  1225.         Arr[start] = tmpe
  1226.         Arr[end] = tmps
  1227.     }
  1228.     return
  1229.     }
  1230.     left = start+0
  1231.     right = end+0
  1232.     sepval = Arr[int((left + right) / 2)]
  1233.     while (left < right) {
  1234.     while (Arr[left] < sepval)
  1235.         left++
  1236.     while (Arr[right] > sepval)
  1237.         right--
  1238.     if (left <= right) {
  1239.         tmp = Arr[left]
  1240.         Arr[left++] = Arr[right]
  1241.         Arr[right--] = tmp
  1242.     }
  1243.     }
  1244.     if (start < right)
  1245.     qsortNumIndByValue(Arr,start,right)
  1246.     if (left < end)
  1247.     qsortNumIndByValue(Arr,left,end)
  1248. }
  1249.  
  1250. ### End qsort routines
  1251.